﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

public class City
{
    #region Member Variables

    // A city has a template (maybe).
    public CityTemplate template = CityTemplate.None;

    // A city has dimensions.
    public int width = 12;
    public int height = 8;

    // A city has buildings on blocks.
    private Dictionary<Vector2, Building> buildings = new Dictionary<Vector2, Building>();
    private List<Building> uniqueBuildings = new List<Building>();

    // A city keeps track of building change events.
    private List<BuildingEvent> buildingEvents = new List<BuildingEvent>();

    private CityScore cityScore = null;
    private RecommendationManager recommendationManager = null;
    private float currentScore = 0.0f;

    #endregion

    #region Enumerations

    public enum CityTemplate
    {
        None
    }

    #endregion

    #region Constructors

    public City(int width, int height)
    {
        this.width = width;
        this.height = height;

        // By default, we're in advanced mode.
        cityScore = new CityScore(this, true);

        recommendationManager = new RecommendationManager(this, cityScore);
    }

    public City(CityTemplate template)
    {
        this.template = template;
        // TODO: Set up width and height based on the template, maybe?
    }

    #endregion

    #region Public Methods

    public void AddBuilding(int x, int y, Building building)
    {
        List<Vector2> locations = GetBuildingLocationsFromSource(x, y, building);

        foreach (Vector2 location in locations)
        {
            if (buildings.ContainsKey(location)) throw new ArgumentException("There is already a building at " + location.ToString() + ". The building present must be removed before another can be added.");
        }

        foreach (Vector2 location in locations)
        {
            buildings.Add(location, building);
        }

        uniqueBuildings.Add(building);

        UnityEngine.MonoBehaviour.print("Pushing create event for type " + building.type);
        buildingEvents.Add(new BuildingEvent(locations, building, BuildingEvent.BuildingEventType.Create));
    }

    public void DestroyBuilding(int x, int y)
    {
        Vector2 location = new Vector2(x, y);

        if (!buildings.ContainsKey(location))
        {
            MonoBehaviour.print("City.DestroyBuilding: No building found at position " + location);
            return;
        }

        Building building = buildings[location];
        // TODO: Clear out every location where this building exists.
        List<Vector2> removePositions = GetBuildingLocationsFromSource(x, y, building);

        foreach (Vector2 position in removePositions)
        {
            buildings.Remove(position);
        }

        uniqueBuildings.Remove(building);

        UnityEngine.MonoBehaviour.print("Pushing Destroy event for type " + building.type);
        BuildingEvent destroyEvent = new BuildingEvent(removePositions, building, BuildingEvent.BuildingEventType.Destroy);
        buildingEvents.Add(destroyEvent);
    }

    public Building GetBuilding(int x, int y)
    {
        Vector2 location = new Vector2(x, y);
        if (!buildings.ContainsKey(location)) return null;

        return buildings[location];
    }

    internal Building GetBuilding(Vector2 location)
    {
        if (!buildings.ContainsKey(location)) return null;
        return buildings[location];
    }

    public List<BuildingEvent> FlushBuildingEvents()
    {
        List<BuildingEvent> returnEvents = new List<BuildingEvent>();

        if (buildingEvents.Count > 0)
        {
            UnityEngine.MonoBehaviour.print("BuildingEvents has " + buildingEvents.Count + " events in queue");
        }

        foreach (BuildingEvent buildingEvent in buildingEvents)
        {
            UnityEngine.MonoBehaviour.print("City.cs processing event of type " + buildingEvent.type + " for building type " + buildingEvent.building.type);
            cityScore.ProcessEvent(buildingEvent);
            currentScore = cityScore.Score;
            UnityEngine.MonoBehaviour.print(cityScore.ScoreSummary);
            returnEvents.Add(buildingEvent);
        }

        buildingEvents.Clear();
        return returnEvents;
    }

    public float Score
    {
        get { return currentScore; }
    }

    #endregion

    #region Private Utility Methods

    private List<Vector2> GetBuildingLocationsFromSource(int x, int y, Building building)
    {
        List<Vector2> locations = new List<Vector2>();
        locations.Add(new Vector2(x, y));
        switch (building.size)
        {
            case Building.BuildingSize.Size1x2:
                // For a 1x2, orientation indicates the direction of the other tile.
                switch (building.orientation)
                {
                    case Building.Orientation.East:
                        locations.Add(new Vector2(x + 1, y));
                        break;
                    case Building.Orientation.West:
                        locations.Add(new Vector2(x - 1, y));
                        break;
                    case Building.Orientation.North:
                        locations.Add(new Vector2(x, y + 1));
                        break;
                    case Building.Orientation.South:
                        locations.Add(new Vector2(x, y - 1));
                        break;
                }
                break;
            case Building.BuildingSize.Size2x2:
                // For a 2x2, orientation indicates the direction to start out from the
                // source point, traveling in a clockwise spiral, to hit all four points.
                switch (building.orientation)
                {
                    case Building.Orientation.East:
                        locations.Add(new Vector2(x + 1, y));
                        locations.Add(new Vector2(x + 1, y - 1));
                        locations.Add(new Vector2(x, y - 1));
                        break;
                    case Building.Orientation.West:
                        locations.Add(new Vector2(x - 1, y));
                        locations.Add(new Vector2(x - 1, y + 1));
                        locations.Add(new Vector2(x, y + 1));
                        break;
                    case Building.Orientation.North:
                        locations.Add(new Vector2(x, y + 1));
                        locations.Add(new Vector2(x + 1, y + 1));
                        locations.Add(new Vector2(x + 1, y));
                        break;
                    case Building.Orientation.South:
                        locations.Add(new Vector2(x, y - 1));
                        locations.Add(new Vector2(x - 1, y - 1));
                        locations.Add(new Vector2(x - 1, y));
                        break;
                }
                break;
        }
        return locations;
    }

    #endregion



    public int BuildingCount { get { return this.uniqueBuildings.Count; } }

    public Dictionary<Vector2, Building> BuildingsByTile { get { return buildings; } }

    internal Recommendation.RecommendationCode GetRecommendationCode()
    {
        return recommendationManager.GetRecommendation();
    }
}
